﻿/* 
 * Copyright (c) Intel Corporation
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * -- Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * -- Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * -- Neither the name of the Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE INTEL OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.ServiceProcess;
using ExtensionLoader;
using ExtensionLoader.Config;
using HttpServer;
using HttpServer.Templates;
using OpenMetaverse;
using OpenMetaverse.Http;

using HttpListener = HttpServer.HttpListener;

namespace InventoryServer
{
    public class InventoryServer : ServiceBase
    {
        public const string DATA_DIR = "CableBeachData/";
        public const string WEB_CONTENT_DIR = DATA_DIR + "webcontent/";
        public const string CONFIG_FILE = DATA_DIR + "InventoryServer.ini";

        public Uri HttpUri;
        public HttpListener HttpServer;
        public X509Certificate2 HttpCertificate;
        public SmartyEngine HttpTemplates = new SmartyEngine();
        public IniConfigSource ConfigFile;
        public List<string> ExtensionList;
        private DatabaseMigration DatabaseMigration;

        public IServiceRegistrationProvider ServiceRegistrationProvider;
        public IAssetProvider AssetProvider;
        public IFilesystemProvider FilesystemProvider;
        public IMetricsProvider MetricsProvider;
        public IPropertyProvider WebDAVPropertyProvider;

        CapsServer CapsServer;
        ExtensionLoader<InventoryServer> extensions = new ExtensionLoader<InventoryServer>();
        HttpServer.Handlers.FileHandler fileHandler;

        public InventoryServer()
        {
            this.ServiceName = "CableBeachInventoryServer";
        }

        public bool Start()
        {
            IPHostEntry entry;
            IPAddress address;
            IConfig httpConfig;
            IConfig migrationConfig;

            string invConnString;
            string assetConnString;
            string defaultAssetSetsFile;
            string defaultLibraryRootID;
            string databaseType;

            #region Config Parsing

            try
            {
                // Load the extension list (and ordering) from our config file
                ConfigFile = new IniConfigSource(CONFIG_FILE);
                httpConfig = ConfigFile.Configs["Http"];
                IConfig extensionConfig = ConfigFile.Configs["Extensions"];
                ExtensionList = new List<string>(extensionConfig.GetKeys());
                
                // Migrate database schema and default assets //

                // Read configs
                migrationConfig = ConfigFile.Configs["OpenSimInventoryDatabase"];
                invConnString = migrationConfig.Get("ConnectionString");
                migrationConfig = ConfigFile.Configs["OpenSimAssetDatabase"];
                databaseType = migrationConfig.Get("DatabaseType");
                assetConnString = migrationConfig.Get("ConnectionString");
                defaultAssetSetsFile = migrationConfig.Get("DefaultAssetsDescriptionFile");
                migrationConfig = ConfigFile.Configs["Inventory"];
                defaultLibraryRootID = migrationConfig.Get("DefaultInventoryLibraryRoot");

                // Instantiate DatabaseMigration
                DatabaseMigration = new DatabaseMigration(databaseType, invConnString, assetConnString, defaultAssetSetsFile);
                
                // Migrate Assets
                DatabaseMigration.MigrateAssetTables();
                // Load default assets to database from description file
                DatabaseMigration.LoadDefaultAssets();

                // Migrate Inventory
                DatabaseMigration.MigrateInventoryStoreTables();
                // Create world library to database
                DatabaseMigration.CreateWorldLibrary(defaultLibraryRootID);
                

            }
            catch (Exception ex)
            {
                Logger.Error("Failed to parse " + CONFIG_FILE + " Code: " + ex.ToString());
                return false;
            }

            #endregion Config Parsing

            #region HTTP Server

            int port = httpConfig.GetInt("ListenPort", 0);
            string hostname = httpConfig.GetString("Hostname", null);
            string sslCertFile = httpConfig.GetString("SSLCertFile", null);

            if (port <= 0)
            {
                Logger.Error("[InventoryServer] Invalid ListenPort value in the config file");
                return false;
            }

            if (String.IsNullOrEmpty(hostname))
            {
                hostname = Dns.GetHostName();
                entry = Dns.GetHostEntry(hostname);
                address = IPAddress.Any;
            }
            else
            {
                entry = Dns.GetHostEntry(hostname);
                if (entry != null && entry.AddressList.Length > 0)
                {
                    address = entry.AddressList[0];
                }
                else
                {
                    Logger.Warn("[InventoryServer] Could not resolve an IP address from hostname " + hostname + ", binding to all interfaces");
                    address = IPAddress.Any;
                }
            }

            if (httpConfig.GetBoolean("SSLServer", false))
            {
                // HTTPS mode
                if (!String.IsNullOrEmpty(sslCertFile))
                {
                    try { HttpCertificate = new X509Certificate2(DATA_DIR + sslCertFile); }
                    catch (Exception ex)
                    {
                        Logger.Error("[InventoryServer] Failed to load SSL certificate file \"" + DATA_DIR + sslCertFile + "\": " + ex.Message);
                        return false;
                    }
                    HttpServer = HttpListener.Create(log4netLogWriter.Instance, address, port, HttpCertificate);
                    HttpUri = new Uri("https://" + hostname + (port != 80 ? (":" + port) : String.Empty));
                }
                else
                {
                    Logger.Error("[InventoryServer] SSLServer is set to true but no valid SSL certificate could be loaded");
                    return false;
                }
            }
            else
            {
                // HTTP mode
                HttpServer = HttpListener.Create(log4netLogWriter.Instance, address, port);
                HttpUri = new Uri("http://" + hostname + (port != 80 ? (":" + port) : String.Empty));
            }

            HttpServer.Set404Handler(NotFoundHandler);
            HttpServer.Start(10);

            // File serving for web templates
            fileHandler = new HttpServer.Handlers.FileHandler(HttpServer, "/content/", WEB_CONTENT_DIR, true);

            // Capabilities handling
            CapsServer = new CapsServer(HttpServer, @"^/caps/");
            CapsServer.Start();

            Logger.Info("[InventoryServer] InventoryServer is listening at " + HttpUri.ToString());

            #endregion HTTP Server

            #region Server Extensions

            try
            {
                // Create a list of references for .cs extensions that are compiled at runtime
                List<string> references = new List<string>();
                references.Add("OpenMetaverseTypes.dll");
                references.Add("OpenMetaverse.dll");
                references.Add("OpenMetaverse.StructuredData.dll");
                references.Add("OpenMetaverse.Http.dll");
                references.Add("ExtensionLoader.dll");
                references.Add("InventoryServer.exe");

                // Load extensions from the current executing assembly, InventoryServer.*.dll assemblies on disk, and
                // InventoryServer.*.cs source files on disk.
                extensions.LoadAllExtensions(Assembly.GetExecutingAssembly(),
                    AppDomain.CurrentDomain.BaseDirectory, ExtensionList, references,
                    "InventoryServer.*.dll", "InventoryServer.*.cs");

                // Automatically assign extensions that implement interfaces to the list of interface
                // variables in "assignables"
                extensions.AssignExtensions(this, extensions.GetInterfaces(this));
            }
            catch (ExtensionException ex)
            {
                Logger.Error("[InventoryServer] Extension loading failed, shutting down: " + ex.Message);
                Shutdown();
                return false;
            }

            // Start all of the extensions
            foreach (IExtension<InventoryServer> extension in extensions.Extensions)
            {
                Logger.Info("[InventoryServer] Starting extension " + extension.GetType().Name);
                extension.Start(this);
            }

            #endregion Server Extensions

            return true;
        }

        public void Shutdown()
        {
            foreach (IExtension<InventoryServer> extension in extensions.Extensions)
            {
                Logger.Debug("[InventoryServer] Stopping extension " + extension.GetType().Name);
                try { extension.Stop(); }
                catch (Exception ex)
                { Logger.ErrorFormat("[InventoryServer] Failure shutting down extension {0}: {1}", extension.GetType().Name, ex.Message); }
            }

            if (HttpServer != null)
                HttpServer.Stop();

#if !DEBUG
            Stop();
#endif
        }

        public Uri CreateCapability(CapsRequestCallback callback, bool clientCertRequired, object state)
        {
            UUID capID = CapsServer.CreateCapability(callback, clientCertRequired, state);
            return new Uri(HttpUri, "/caps/" + capID);
        }

        void NotFoundHandler(IHttpClientContext context, IHttpRequest request, IHttpResponse response)
        {
            Logger.Warn("[InventoryServer] Returning 404 for " + request.Method + " " + request.UriPath);
            response.Status = HttpStatusCode.NotFound;
            response.AddToBody("<html><head><title>Not Found</title></head><body><h3>The requested document was not found</h3></body></html>");
        }

        #region ServiceBase Overrides

        protected override void OnStart(string[] args)
        {
            Start();
        }
        protected override void OnStop()
        {
            Shutdown();
        }

        #endregion
    }
}
